package it.unibo.refolding.pico.stripchart;

import it.unibo.refolding.common.CommonThings;
import it.unibo.refolding.common.DoublePair;
import it.unibo.refolding.common.LinearFunction;
import it.unibo.refolding.pico.ApproachRetractionLoop;
import it.unibo.refolding.pico.ContactPointAndSlopeHelper;
import it.unibo.refolding.pico.PeakOrNot;
import it.unibo.refolding.pico.PicoDataAnalyser;
import it.unibo.refolding.pico.PicoDataAnalyser.ContactPointAndSlope;
import it.unibo.refolding.pico.PicoDataAnalyser.ContactPointsAndSlope;
import it.unibo.refolding.pico.PicoDataAnalyser.PeakAnalysisInput;
import it.unibo.refolding.pico.PicoDataExtractor;
import it.unibo.refolding.pico.PlainPicoData;
import it.unibo.refolding.pico.RedundantPicoCurveInfo;
import it.unibo.refolding.pico.StripChartPicoData;
import it.unibo.refolding.pico.Wlc;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.shorts.ShortArrayList;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.Parser;
import org.apache.commons.io.FileUtils;

import com.google.common.base.Function;
import com.google.common.base.Predicate;

public class TraceExporter {

  public static void main(String[] args) throws IOException, ParseException {
    System.setProperty("java.awt.headless", "true");

    Options options = new Options();

    Option dataPathOption = new Option("dataPath", true, "data file");
    dataPathOption.setRequired(true);
    dataPathOption.setType(String.class);
    options.addOption(dataPathOption);

    Option jpPathOption = new Option("jpPath", true, "data file");
    jpPathOption.setRequired(true);
    jpPathOption.setType(String.class);
    options.addOption(jpPathOption);

    Option cpsPathOption = new Option("cpsPath", true, "data file");
    cpsPathOption.setRequired(true);
    cpsPathOption.setType(String.class);
    options.addOption(cpsPathOption);

    Option minPeakForcePnOption = AnalysisOptions.createMinPeakForcePnOption();
    options.addOption(minPeakForcePnOption);

    Option minPeakContourLengthNmOption = AnalysisOptions.createMinPeakContourLengthNmOption();
    options.addOption(minPeakContourLengthNmOption);

    Option minContourLengthDeltaNmOption = AnalysisOptions.createMinContourLengthDeltaNmOption();
    options.addOption(minContourLengthDeltaNmOption);

    Option minPeakForceRelativeToEmpiricalBaselinePnOption = AnalysisOptions.createMinPeakForceRelativeToEmpiricalBaselinePnOption();
    options.addOption(minPeakForceRelativeToEmpiricalBaselinePnOption);

    Option medianAbsoluteDeviationLowerBoundPnOption = AnalysisOptions.createMedianAbsoluteDeviationLowerBoundPnOption();
    options.addOption(medianAbsoluteDeviationLowerBoundPnOption);

    Option leftMostlyHigherWindowSizeNmOption = AnalysisOptions.createLeftMostlyHigherWindowSizeNmOption();
    options.addOption(leftMostlyHigherWindowSizeNmOption);
    Option leftMostlyHigherFractionOption = AnalysisOptions.createLeftMostlyHigherFractionOption();
    options.addOption(leftMostlyHigherFractionOption);
    Option leftMostlyHigherPnOption = AnalysisOptions.createLeftMostlyHigherPnOption();
    options.addOption(leftMostlyHigherPnOption);

    Option rightMuchHigherPnOption = AnalysisOptions.createRightMuchHigherPnOption();
    options.addOption(rightMuchHigherPnOption);

    Option atMostOnePeakWindowNmOption = AnalysisOptions.createAtMostOnePeakWindowNmOption();
    options.addOption(atMostOnePeakWindowNmOption);

    Option temperatureKOption = AnalysisOptions.createTemperatureKOption();
    options.addOption(temperatureKOption);

    Option persistenceLengthNmOption = AnalysisOptions.createPersistenceLengthNmOption();
    options.addOption(persistenceLengthNmOption);

    Option tracePathOption = new Option("tracePath", true, "data file");
    tracePathOption.setRequired(true);
    tracePathOption.setType(String.class);
    options.addOption(tracePathOption);

    Parser parser = new GnuParser();
    CommandLine line = parser.parse(options, args);

    String dataPath = line.getOptionValue(dataPathOption.getOpt());
    String jpPath = line.getOptionValue(jpPathOption.getOpt());
    String cpsPath = line.getOptionValue(cpsPathOption.getOpt());
    String tracePath = line.getOptionValue(tracePathOption.getOpt());
    Function<? super Double, Double> minPeakForceN = AnalysisOptions.minPeakForceN(
      line, minPeakForcePnOption);
    double minPeakContourLengthM = AnalysisOptions.minPeakContourLengthM(
      line, minPeakContourLengthNmOption);
    double minContourLengthDeltaM = AnalysisOptions.minContourLengthDeltaM(
      line, minContourLengthDeltaNmOption);
    double minPeakForceRelativeToEmpiricalBaselineN = AnalysisOptions.minPeakForceRelativeToEmpiricalBaselineN(
      line, minPeakForceRelativeToEmpiricalBaselinePnOption);
    Function<? super Double, Double> medianAbsoluteDeviationLowerBoundN = AnalysisOptions.medianAbsoluteDeviationLowerBoundN(
      line, medianAbsoluteDeviationLowerBoundPnOption);
    double leftMostlyHigherWindowSizeM = AnalysisOptions.leftMostlyHigherWindowSizeM(
      line, leftMostlyHigherWindowSizeNmOption);
    final double leftMostlyHigherFraction = AnalysisOptions.leftMostlyHigherFraction(
      line, leftMostlyHigherFractionOption);
    final double leftMostlyHigherN = AnalysisOptions.leftMostlyHigherN(
      line, leftMostlyHigherPnOption);
    
    Function<? super Double, Double> rightMuchHigherThanAllN = AnalysisOptions.rightMuchHigherN(
      line, rightMuchHigherPnOption);
    Function<? super Double, Double> rightMuchHigherN = AnalysisOptions.rightMuchHigherN(
      line, rightMuchHigherPnOption);
    double atMostOnePeakWindowM = AnalysisOptions.atMostOnePeakWindowM(
      line, atMostOnePeakWindowNmOption);
    double temperatureK = AnalysisOptions.temperatureK(line, temperatureKOption);
    double persistenceLengthM = AnalysisOptions.persistenceLengthM(line, persistenceLengthNmOption);

    StripChartPicoData stripChart = (StripChartPicoData) PicoDataExtractor.parse(Files.readAllBytes(Paths.get(dataPath)));
    IntList jp = JpHelper.extractJunctionPoints(Paths.get(jpPath));
    double hoverTimeS = JpHelper.readWaitingTimeS(jp);
    final ContactPointAndSlope retractionContactPointAndSlope = ContactPointAndSlopeHelper.extractContactPointAndSlope(Paths.get(cpsPath));
    ApproachRetractionLoop loop = StripChartRefolding.createLoopGivenFetchRefoldBreakJunctionPoints(
      jp, stripChart, null);
    PlainPicoData fetch = loop.iterations[0];
    ContactPointsAndSlope contactPointsAndSlope = ContactPointAndSlopeHelper.bothContactPointsAndSlope(
      fetch, retractionContactPointAndSlope);
    PeakAnalysisInput peakAnalysisInput = new PeakAnalysisInput(
      fetch, contactPointsAndSlope, minPeakForceN.apply(fetch.sharedRetraction().velocity),
      minPeakContourLengthM, minContourLengthDeltaM, minPeakForceRelativeToEmpiricalBaselineN,
      medianAbsoluteDeviationLowerBoundN.apply(fetch.sharedRetraction().velocity),
      leftMostlyHigherWindowSizeM, leftMostlyHigherFraction, leftMostlyHigherN,
      rightMuchHigherThanAllN.apply(fetch.sharedRetraction().velocity), rightMuchHigherN.apply(fetch.sharedRetraction().velocity),
      atMostOnePeakWindowM, Wlc.KB * temperatureK / persistenceLengthM);
    RedundantPicoCurveInfo fetchInfo = PicoDataAnalyser.analyseWithGivenContactPointsAndSlope(peakAnalysisInput);
    ShortArrayList sal = new ShortArrayList(fetch.sharedRetraction().sharedZArray());
    Collections.reverse(sal);
    int len = CommonThings.<Number>findContiguousSegmentLengthAtStart(sal, new Predicate<Number>() {
      @Override
      public boolean apply(Number n) {
        return n.doubleValue() < retractionContactPointAndSlope.zLsb;
      }
    });
    short[] fetchZ = fetch.sharedRetraction().sharedZArray();
    short[] fetchDeflection = fetch.sharedRetraction().sharedDeflectionArray();

    double[] fetchZM = zM(stripChart, len, fetchZ, retractionContactPointAndSlope);
    double[] fetchFN = fN(stripChart, len, fetchZ, fetchDeflection, retractionContactPointAndSlope);

    PlainPicoData refolding = loop.iterations[1];
    short[] refoldingZ = refolding.sharedRetraction().sharedZArray();
    short[] refoldingDeflection = refolding.sharedRetraction().sharedDeflectionArray();
    double[] refoldingZM = zM(
      stripChart, refoldingZ.length, refoldingZ, retractionContactPointAndSlope);
    double[] refoldingFN = fN(
      stripChart, refoldingZ.length, refoldingZ, refoldingDeflection,
      retractionContactPointAndSlope);
    double[] clM = PeakOrNot.contourLengths(fetchInfo.peakOrNots).toDoubleArray();

    DoublePair hoverZDef = JpHelper.hoverZDef(jp, stripChart);
    double hoverZM = peakAnalysisInput.plainPicoData.zLsbToMetresFactor
      * (fetchInfo.retractionContactPoint.zLsb - hoverZDef.x);
    double hoverFN = (hoverZDef.y - retractionContactPointAndSlope.baseline().evaluate(hoverZDef.x))
      * stripChart.deflectionLsbToNewtonsFactor();
    String s = String.format(
      Locale.ROOT,
      "RefoldingTrace(kb_t_over_p=%s, k_n_per_m=%s, hover_time_s=%s, hover_z_m=%s, hover_f_n=%s, fetch_z_m=%s, fetch_f_n=%s, refolding_z_m=%s, refolding_f_n=%s, cl_m=%s)",
      Double.toString(Wlc.KB * temperatureK / persistenceLengthM),
      Double.toString(stripChart.springConstantNPerM), Double.toString(hoverTimeS),
      Double.toString(hoverZM), Double.toString(hoverFN), Arrays.toString(fetchZM),
      Arrays.toString(fetchFN), Arrays.toString(refoldingZM), Arrays.toString(refoldingFN),
      Arrays.toString(clM));
    FileUtils.writeStringToFile(new File(tracePath), s + "\n", "ISO-8859-1");
  }

  private static double[] fN(
    StripChartPicoData stripChart, int len, short[] z, short[] deflection,
    ContactPointAndSlope retractionCps) {
    LinearFunction baseline = retractionCps.baseline();
    double[] fN = new double[len];
    for (int i = len; --i >= 0;) {
      double relativeDef = deflection[z.length - 1 - i] - baseline.evaluate(z[z.length - 1 - i]);
      fN[i] = relativeDef * stripChart.deflectionLsbToNewtonsFactor();
    }
    return fN;
  }

  private static double[] zM(
    StripChartPicoData stripChart, int len, short[] z, ContactPointAndSlope retractionCps) {
    double[] zM = new double[len];
    for (int i = len; --i >= 0;) {
      zM[i] = stripChart.zLsbToMetresFactor * (retractionCps.zLsb - z[z.length - 1 - i]);
    }
    return zM;
  }
}
